#include "Document.h"

#include "ItemModels/ComponentListModel.h"
#include "ItemModels/FeatureSetListModel.h"
#include "ItemModels/HoleListModel.h"
#include "ItemModels/LayerListModel.h"
#include "ItemModels/NetListModel.h"
#include "ItemModels/PackageListModel.h"
#include "ItemModels/PadListModel.h"
#include "ItemModels/SpecificationTreeModel.h"
#include "ItemModels/LayerStackupModel.h"

#include "LeIpc2581/DocumentParser.h"
#include "LeIpc2581/Ipc2581.h"

#include <QDebug>
#include <QFile>
#include <QPointF>

#include <QXmlStreamReader>

Document::Document(QObject *parent):
    QObject(parent),
    m_layerListModel(new LayerListModel(this)),
    m_componentListModel(new ComponentListModel(this)),
    m_netListModel(new NetListModel(this)),
    m_padListModel(new PadListModel(this)),
    m_holeListModel(new HoleListModel(this))
{

}

Document::~Document()
{

}

bool Document::open(const QString &fileName)
{
    m_errorString.clear();
    m_ipc.reset();
    m_step = nullptr;
    m_layerListModel->clear();
    m_componentListModel->clear();
    m_netListModel->clear();
    m_padListModel->clear();

    QFile file(fileName);
    if (!file.open(QFile::ReadOnly))
    {
        m_errorString = QString("Unable to open file '%1': %2")
                .arg(fileName)
                .arg(file.errorString());
        return false;
    }

    QXmlStreamReader reader(&file);
    reader.readNextStartElement(); // IPC-2581
    Ipc2581b::DocumentParser parser;
    bool success = parser.parse(&reader);
    if (!success)
    {
        m_errorString = QString("Error in file '%1', line %2, column %3, character %4: %5")
                .arg(file.fileName())
                .arg(reader.lineNumber())
                .arg(reader.columnNumber())
                .arg(reader.characterOffset())
                .arg(reader.errorString());
        return false;
    }

    m_ipc.reset(parser.result());

    if (m_ipc->ecad->cadData->stepList.count() != 1)
    {
        m_errorString = QString("Multi-steps not supported");
        return false;
    }
    m_step = m_ipc->ecad->cadData->stepList.value(0);

    // padstack
    // padstackDef
    // route

    m_specTreeModel = new SpecificationTreeModel(m_ipc->ecad->cadHeader->specList);

    for (const Ipc2581b::Layer *layer: m_ipc->ecad->cadData->layerList)
        m_layerListModel->addLayer(layer);

    for (const Ipc2581b::Stackup *stackup: m_ipc->ecad->cadData->stackupList)
    {
        auto model = new LayerStackupModel(this);
        model->setIpcStackup(this, stackup);
        m_layerStackModelMap.insert(stackup->nameOptional.value, model);
    }

    m_packageListModel = new PackageListModel(this, m_step->packageList);

    for (const Ipc2581b::Component *component: m_step->componentList)
        m_componentListModel->addComponent(component);

    for (const Ipc2581b::LogicalNet *net: m_step->logicalNetList)
        m_netListModel->addLogicalNet(net);

    for (const Ipc2581b::PhyNetGroup *group: m_step->phyNetGroupList)
        for (const Ipc2581b::PhyNet *net: group->phyNetList)
            m_netListModel->addPhysicalNet(net);

    // TBD: layerFeature->layerRef
    // TBD: set->{net|polarity|padUsage|testPoint|geometry|plate|componentRef}Optional
    // TBD: set->nonstandardAttributeList
    // TBD: set->specRefList
    // => a set acts as a decorator for evey feature in the set
    //   - standard attributes/properties: net|polarity|padUsage|testPoint|geometry|plate|componentRef
    //   - non standard attributes properties
    //   - spec/req properties
    // => m_padListModel->addPad(pad, infoMap, userMap, specMap);
    for (auto layerFeature: m_step->layerFeatureList)
    {
        if (!m_featureSetListModelMap.contains(layerFeature->layerRef))
            m_featureSetListModelMap.insert(layerFeature->layerRef, new FeatureSetListModel(this));
        //auto featureSetListmodel = m_featureSetListModelMap.value(layerFeature->layerRef);
        auto featureSetListmodel = m_featureSetListModelMap.first(); // FIXME: temporary code
        for (auto set: layerFeature->setList)
        {
            m_layerListModel->addFeatureSet(layerFeature->layerRef, set);
            featureSetListmodel->addFeatureSet(set);

            for (auto features: set->featuresList)
                Q_UNUSED(features);

            for (auto pad: set->padList)
                m_padListModel->addPad(pad);

            for (auto hole: set->holeList)
                m_holeListModel->addHole(hole);

            for (auto fiducial: set->fiducialList)
                Q_UNUSED(fiducial);

            for (auto slot: set->slotCavityList)
                Q_UNUSED(slot);
        }
    }

    m_packageListModel->documentInitialised();

    return true;
}


QString Document::errorString() const
{
    return m_errorString;
}

const Ipc2581b::Content *Document::content() const
{
    if (m_ipc == nullptr)
        return nullptr;
    return m_ipc->content;
}

QString Document::stepName() const
{
    if (m_step == nullptr)
        return QString();
    return m_step->name;
}

const Ipc2581b::Contour *Document::stepProfile() const
{
    if (m_step == nullptr)
        return nullptr;
    return m_step->profile;
}

QPointF Document::stepDatum() const
{
    if (m_step == nullptr)
        return QPointF();
    return QPointF(m_step->datum->x, m_step->datum->y);
}

LayerListModel *Document::layerListModel() const
{
    return m_layerListModel;
}

ComponentListModel *Document::componentListModel() const
{
    return m_componentListModel;
}

PackageListModel *Document::packageListModel() const
{
    return m_packageListModel;
}

NetListModel *Document::netListModel() const
{
    return m_netListModel;
}

PadListModel *Document::padListModel() const
{
    return m_padListModel;
}

HoleListModel *Document::holeListModel() const
{
    return m_holeListModel;
}

FeatureSetListModel *Document::featureSetListModel() const
{
    return m_featureSetListModelMap.first(); // FIXME: temporary code
}

SpecificationTreeModel *Document::specificationTreeModel() const
{
    return m_specTreeModel;
}

LayerStackupModel *Document::layerStackModel() const
{
    if (m_layerStackModelMap.isEmpty())
    {
        qWarning() << "No stackup model available";
        return nullptr;
    }
    return m_layerStackModelMap.first(); // // FIXME: temporary code
}
